//	--------------------------------------------------------------------
//	Copyright(C) 2006,2007 Zhao Yukun. All rights reserved.
//	This file is a part of the Xeres system.
//	Xeres 2007
//	Contact : xeres.engine@gmail.com
//	$(reservedInfo)
//	$(reservedDeclaration)
//	--------------------------------------------------------------------
///	@file	<CDirectoryData.cpp>
///	@path	~/src/database/xdata/
///	@date	2007/11/26
///	@desc	Local file directory entry.

#include "config/config.h"

#include <set>
#include <map>

#include "database/xdata/CDirectoryData.h"

namespace xeres {

	IMPL_IOBJECT_CLASS( CDirectoryData );

	// ctor
	CDirectoryData::CDirectoryData( RefWeak<IFileSystem> filesystem , const WString& path , const WString& as_path )
	{
		// test if need re-cache
		DataInfo info;
		if( !filesystem->GetFileInfo( path , info ) )
		{
			TRACE_ERROR( _S("CDirectoryData::Construct: Failed to get directory info.") );
			FATAL_ABORT( _S("IO error while getting directory info.") );
		}

		m_filesystem = filesystem;
		m_path = path;

		assert( m_path.size() > 0 );
		if( m_path[m_path.size() - 1] != '/' )
			m_path.push_back( '/' );

		TRACE_INFO( _S("Caching directory <%s>...") , m_path.c_str() );

		// make key
		size_t len = as_path.size();
		if( len >= 2 )
		{
			size_t start = as_path.find_last_of( '/' , len - 2 ) + 1;
			size_t end = as_path.find_first_of( '/' , start );
			m_key = Key::MakeKey( as_path.substr( start , end - start ) );
		}
		else // len == 1 that's '/'
		{
			m_key = Key::MakeKey( as_path.substr( 0 , 0 ) );
		}

		// cache dirs
		m_filesystem->EnumerateFiles( m_path , _S("*.*") ,
			boost::bind( &CDirectoryData::CacheDirCallback , this , _1 ) );

		// update time
		m_modified = info.m_modified;
	}

	// dtor
	CDirectoryData::~CDirectoryData( void )
	{
	}

	// GetVarTypeName
	const WString& CDirectoryData::GetVarTypeName( void ) const
	{
		static const WString s_VoidTypeName( VOID_TYPE_CLASS_NAME );
		return s_VoidTypeName;
	}

	// GetVarTypeID
	const ClassID CDirectoryData::GetVarTypeID( void ) const
	{
		return VOID_TYPE_CLASS_ID;
	}

	// GetInfo
	bool CDirectoryData::GetInfo( DataInfo& datainfo )
	{
		m_filesystem->GetFileInfo( m_path , datainfo );
		datainfo.m_type = DATA_DIR;
		datainfo.m_size = 0;
		return true;
	}

	// GetKey
	const Key& CDirectoryData::GetKey( void ) const
	{
		return m_key;
	}

	// NewChild
	RefPass<IData> CDirectoryData::NewChild( const Key& key )
	{
		// first we find out what we're going to create

		WString str = key.GetString();
		WString ext;
		size_t dot = str.find_last_of( '.' );
		if( dot == WString::npos )
		{
			// no file extension, about to create a directory
			WString path = m_path + str;
			if( !m_filesystem->CreateDirectory( path ) )
				return NULL;

			RefWeak<CDirectoryData> dir = new CDirectoryData( m_filesystem , path , path );
			m_dirs.push_back( dir );
			return dir;
		}
		else
		{
			// ask file system for file
			return AskForFile( key , true );
		}
	}

	// GetChildCount
	size_t CDirectoryData::GetChildCount( void )
	{
		return m_dirs.size() + m_files.size();
	}

	// GetChild
	RefPass<IData> CDirectoryData::GetChild( const Key& key )
	{
		// first we find all directories
		for( size_t i = 0 , total = m_dirs.size() ; i < total ; ++i )
		{
			if( m_dirs[i]->GetKey() == key )
				return m_dirs[i];
		}

		// then we try to match files
		for( size_t i = 0 , total = m_files.size() ; i < total ; ++i )
		{
			if( m_files[i] == key )
			{
				return AskForFile( key , false );
			}
		}
		return NULL;
	}

	// GetChild
	RefPass<IData> CDirectoryData::GetChild( size_t index )
	{
		// first we find all directories
		if( index < m_dirs.size() )
		{
			return m_dirs[ index ];
		}
		else if( index < m_dirs.size() + m_files.size() )
		{
			return AskForFile( m_files[ index - m_dirs.size() ].GetCStr() , false );
		}
		else
		{
			return NULL;
		}
	}

	// GetChildInfo
	bool CDirectoryData::GetChildInfo( const Key& key , DataInfo& info )
	{
		// first we find all directories
		for( size_t i = 0 , total = m_dirs.size() ; i < total ; ++i )
		{
			if( m_dirs[i]->GetKey() == key )
			{
				return m_dirs[i]->GetInfo( info );
			}
		}

		// then we try to match files
		for( size_t i = 0 , total = m_files.size() ; i < total ; ++i )
		{
			if( m_files[i] == key )
			{
				return m_filesystem->GetFileInfo( m_path + key.GetCStr() , info );
			}
		}
		return false;
	}

	// GetChildInfo
	bool CDirectoryData::GetChildInfo( size_t index , DataInfo& info , WString& name )
	{
		// first we find all directories
		if( index < m_dirs.size() )
		{
			if( m_dirs[ index ]->GetInfo( info ) )
			{
				name = m_dirs[ index ]->GetKey().GetCStr();
				return true;
			}
			return false;
		}
		else if( index < m_dirs.size() + m_files.size() )
		{
			const Key& key = m_files[ index - m_dirs.size() ];
			name = key.GetCStr();
			return m_filesystem->GetFileInfo( m_path + name , info );
		}
		else
		{
			return false;
		}
	}

	// FindChildIndex
	bool CDirectoryData::FindChildIndex( const Key& key , size_t& index )
	{
		if( index < m_dirs.size() )
		{
			// first we find all directories
			for( size_t i = index , total = m_dirs.size() ; i < total ; ++i , ++index )
			{
				if( m_dirs[i]->GetKey() == key )
					return true;
			}
		}
		if( index < m_dirs.size() + m_files.size() )
		{
			// then we try to match files
			for( size_t i = index = m_dirs.size() ; i < m_files.size() ; ++i , ++index )
			{
				if( m_files[i] == key )
				{
					return true;
				}
			}
		}
		index = npos;
		return false;
	}

	// FindChild
	RefPass<IData> CDirectoryData::FindChild( const Key& key , size_t& index )
	{
		if( index < m_dirs.size() )
		{
			// first we find all directories
			for( size_t i = index ; i < m_dirs.size() ; ++i , ++index )
			{
				if( m_dirs[i]->GetKey() == key )
					return m_dirs[i];
			}
		}
		if( index < m_dirs.size() + m_files.size() )
		{
			// then we try to match files
			for( size_t i = index - m_dirs.size() ; i < m_files.size() ; ++i , ++index )
			{
				if( m_files[i] == key )
				{
					return AskForFile( m_files[i] , false );
				}
			}
		}
		index = npos;
		return NULL;
	}

	// Save
	bool CDirectoryData::Save( void )
	{
		return false;
	}

	// Reload
	bool CDirectoryData::Reload( void )
	{
		return false;
	}

	// AskForFile
	RefPass<IData> CDirectoryData::AskForFile( const Key& file , bool create_new )
	{
		// composite full path
		WString full = m_path + file.GetCStr();

		// ask for file cache

		if( create_new )
			return m_filesystem->CreateEntryFromFile( full );
		else
			return m_filesystem->LoadEntryFromFile( full );
	}

	void CDirectoryData::CacheDirCallback(
		void * data , const IFileSystem::IEnumFileData& iefd )
	{
		CDirectoryData * entry = static_cast<CDirectoryData*>(data);
		if( iefd.type == DATA_DIR )
		{
			WString path = entry->m_path + iefd.name;
 			// build new entry
			RefWeak<CDirectoryData> dir = new CDirectoryData( entry->m_filesystem , path , path );
			// save new
			entry->m_dirs.push_back( dir );
		}
		else
		{
			entry->m_files.push_back( Key::MakeKey( iefd.name ) );
		}
	}

	// OpenFile
	RefPass<IFile> CDirectoryData::OpenFile( const WString& file )
	{
		// composite full path
		WString full = m_path + file;
		return m_filesystem->OpenFile( full );
	}

	// OpenIStream
	struct IStream * CDirectoryData::OpenIStream( const WString& file )
	{
		// composite full path
		WString full = m_path + file;
		return m_filesystem->OpenIStream( full );
	}

} // namespace xeres
